package main

import (
	"fmt"
	"image"
	"image/jpeg"
	rt "main/raytracing"
	"math"
	"os"
	"time"
)

type T interface {
	Hello() string
}

func randomScene() rt.Hittable {
	world := rt.NewHittableList()

	ground_material := rt.NewLambertian(rt.V(0.5, 0.5, 0.5))
	world.Add(rt.NewSphere(rt.V(0, -1000, 0), 1000, ground_material))

	for a := -11; a < 11; a++ {
		for b := -11; b < 11; b++ {
			choose_mat := rt.Random()
			center := rt.V(
				float64(a)+0.9*rt.Random(),
				0.2,
				float64(b)+0.9*rt.Random(),
			)

			if center.Sub(rt.V(4, 0.2, 0)).Length() > 0.9 {
				if choose_mat < 0.8 {
					albedo := rt.RandomVec3().Mul(rt.RandomVec3())
					sphere_material := rt.NewLambertian(albedo)
					world.Add(rt.NewSphere(center, 0.2, sphere_material))
				} else if choose_mat < 0.95 {
					albedo := rt.RandomBetweenVec3(0.5, 1)
					fuzz := rt.RandomBetween(0, 0.5)
					sphere_material := rt.NewMetal(albedo, fuzz)
					world.Add(rt.NewSphere(center, 0.2, sphere_material))
				} else {
					sphere_material := rt.NewDielectric(1.5)
					world.Add(rt.NewSphere(center, 0.2, sphere_material))
				}
			}
		}
	}

	material1 := rt.NewDielectric(1.5)
	world.Add(rt.NewSphere(rt.V(0, 1, 0), 1.0, material1))
	material2 := rt.NewLambertian(rt.V(0.4, 0.2, 0.1))
	world.Add(rt.NewSphere(rt.V(-4, 1, 0), 1, material2))
	material3 := rt.NewMetal(rt.V(0.7, 0.6, 0.5), 0.0)
	world.Add(rt.NewSphere(rt.V(4, 1, 0), 1, material3))

	return world
}

func rayColor(r *rt.Ray, world rt.Hittable, depth int) rt.Vec3 {
	if depth < 0 {
		return rt.V(0, 0, 0)
	}

	var rec rt.HitRecord
	if world.Hit(r, 0.001, math.MaxFloat64, &rec) {
		attenuation, scattered, bScattered := rec.Material.Scatter(r, &rec)

		if bScattered {
			return attenuation.Mul(
				rayColor(&scattered, world, depth-1),
			)
		}
		return rt.V(0, 0, 0)
	}

	unitDirection := r.Direction.UnitVector()
	t := (unitDirection.Y + 1.0) * 0.5

	return rt.V(1, 1, 1).MulScalar(1.0 - t).Add(rt.V(0.5, 0.7, 1.0).MulScalar((t)))
}

func main() {
	aspectRatio := 3.0 / 2.0
	imageWidth := 400
	imageHeight := int(float64(imageWidth) / aspectRatio)
	samplesPerPixel := 100
	maxDepth := 50

	lookfrom := rt.V(13, 2, 3)
	lookup := rt.V(0, 0, 0)
	vup := rt.V(0, 1, 0)
	vfov := 90.0
	cam := rt.NewCamera(lookfrom, lookup, vup, vfov, aspectRatio)

	world := randomScene()
	// world := rt.NewHittableList()
	// material_ground := rt.NewLambertian(rt.V(0.8, 0.8, 0))
	// material_center := rt.NewLambertian(rt.V(0.1, 0.2, 0.5))
	// material_left := rt.NewDielectric(1.5)
	// material_right := rt.NewMetal(rt.V(0.8, 0.6, 0.2), 0.0)
	// world.Add(rt.NewSphere(rt.V(0, -100.5, -1), 100, material_ground))
	// world.Add(rt.NewSphere(rt.V(0, 0, -1), 0.5, material_center))
	// world.Add(rt.NewSphere(rt.V(-1, 0, -1), 0.5, material_left))
	// world.Add(rt.NewSphere(rt.V(-1, 0, -1), -0.45, material_left))
	// world.Add(rt.NewSphere(rt.V(1, 0, -1), 0.5, material_right))

	img := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))

	start := time.Now()
	for y := imageHeight - 1; y >= 0; y-- {
		fmt.Printf("Scanlines remaining: %d\n", y)
		for x := imageWidth - 1; x >= 0; x-- {
			i := x
			j := imageHeight - y

			var pixel rt.Vec3

			for s := 0; s < samplesPerPixel; s++ {
				u := (float64(i) + rt.Random()) / float64(imageWidth-1)
				v := (float64(j) + rt.Random()) / float64(imageHeight-1)
				r := cam.GetRay(u, v)
				pixel = pixel.Add(rayColor(r, world, maxDepth))
			}

			img.Set(x, y, pixel.ToColor(samplesPerPixel))
		}
	}

	elapsed := time.Since(start)

	fmt.Printf("Elapsed: %s", elapsed)

	f, err := os.Create("./out/a.jpeg")
	if err != nil {
		panic(err)
	}

	defer f.Close()

	err = jpeg.Encode(f, img, &jpeg.Options{Quality: 100})

	if err != nil {
		panic(err)
	}
}
